﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows;

using ABCat.Core;
using ABCat.Core.Plugins;
using ABCat.Core.Plugins.Catalog.FilteringLogics;
using ABCat.Core.Plugins.DataSets;
using ABCat.Shared;
using ABCat.Shared.Properties;

namespace ABCat.Plugins.FilteringLogics.Standard
{
	[PluginCreator("Standard records filtering logic", WorkModeEnum.PerContext)]
	public class FilteringLogicStandard : FilteringLogicPluginBase
	{
		#region + Fields +

		private readonly ConcurrentDictionary<string, ObservableCollection<string>> _filterValuesCache = new ConcurrentDictionary<string, ObservableCollection<string>>();

		private string _author;
		private string _bitrate;
		private IEnumerable<IAudioBook> _booksCache;
		private string _description;
		private string _genre;
		private Dictionary<string, bool> _hiddenCache;
		private bool _isHidden = true;
		private bool _isLoaded = true;
		private Dictionary<string, bool> _loadedCache;
		private string _parsedLenght;
		private string _publisher;
		private string _reader;
		private string _title;

		#endregion
		#region + Properties +

		[Category("Произведение")]
		[DisplayName("Автор")]
		[Description("Автор произведения")]
		[Editor(typeof(FilteredComboBoxEditor), typeof(FilteredComboBoxEditor))]
		public string Author
		{
			get { return _author; }
			set
			{
				if (value == _author) return;
				_author = value;
				OnPropertyChanged();
			}
		}

		[Category("Аудиокнига")]
		[DisplayName("Битрейт")]
		[Description("Битрейт (качество аудикниги)")]
		[Editor(typeof(FilteredComboBoxEditor), typeof(FilteredComboBoxEditor))]
		public string Bitrate
		{
			get { return _bitrate; }
			set
			{
				if (value == _bitrate) return;
				_bitrate = value;
				OnPropertyChanged();
			}
		}

		[Category("Произведение")]
		[DisplayName("Описание")]
		[Description("Описание произведения")]
		public string Description
		{
			get { return _description; }
			set
			{
				if (value == _description) return;
				_description = value;
				OnPropertyChanged();
			}
		}

		[Category("Произведение")]
		[DisplayName("Жанр")]
		[Description("Жанр произведения")]
		[Editor(typeof(FilteredComboBoxEditor), typeof(FilteredComboBoxEditor))]
		public string Genre
		{
			get { return _genre; }
			set
			{
				if (value == _genre) return;
				_genre = value;
				OnPropertyChanged();
			}
		}

		[Browsable(false)]
		public override bool IsEmpty
		{
			get
			{
				return IsHidden
								&& IsLoaded
								&& string.IsNullOrEmpty(Author)
								&& string.IsNullOrEmpty(Bitrate)
								&& string.IsNullOrEmpty(Description)
								&& string.IsNullOrEmpty(Genre)
								&& string.IsNullOrEmpty(ParsedLenght)
								&& string.IsNullOrEmpty(Publisher)
								&& string.IsNullOrEmpty(Reader)
								&& string.IsNullOrEmpty(Title);
			}
		}

		[Category("Каталог")]
		[DisplayName("Скрыто")]
		[Description("Было скрыто из списков")]
		public bool IsHidden
		{
			get { return _isHidden; }
			set
			{
				if (value.Equals(_isHidden)) return;
				_isHidden = value;
				OnPropertyChanged();
			}
		}

		[Category("Каталог")]
		[DisplayName("Загружено")]
		[Description("Было загружено")]
		public bool IsLoaded
		{
			get { return _isLoaded; }
			set
			{
				if (value.Equals(_isLoaded)) return;
				_isLoaded = value;
				OnPropertyChanged();
			}
		}

		[Category("Аудиокнига")]
		[DisplayName("Длительность")]
		[Description("Длительность воспроизведения")]
		[Editor(typeof(FilteredComboBoxEditor), typeof(FilteredComboBoxEditor))]
		public string ParsedLenght
		{
			get { return _parsedLenght; }
			set
			{
				if (value == _parsedLenght) return;
				_parsedLenght = value;
				OnPropertyChanged();
			}
		}

		[Category("Аудиокнига")]
		[DisplayName("Издатель")]
		[Description("Издатель произведения")]
		[Editor(typeof(FilteredComboBoxEditor), typeof(FilteredComboBoxEditor))]
		public string Publisher
		{
			get { return _publisher; }
			set
			{
				if (value == _publisher) return;
				_publisher = value;
				OnPropertyChanged();
			}
		}

		[Category("Аудиокнига")]
		[DisplayName("Исполнитель")]
		[Description("Исполнитель произведения")]
		[Editor(typeof(FilteredComboBoxEditor), typeof(FilteredComboBoxEditor))]
		public string Reader
		{
			get { return _reader; }
			set
			{
				if (value == _reader) return;
				_reader = value;
				OnPropertyChanged();
			}
		}

		[Category("Произведение")]
		[DisplayName("Название")]
		[Description("Назавание произведения")]
		public string Title
		{
			get { return _title; }
			set
			{
				if (value == _title) return;
				_title = value;
				OnPropertyChanged();
			}
		}

		#endregion
		#region + Ctor +

		[UsedImplicitly]
		public FilteringLogicStandard(Context context)
			: base(context)
		{
			RegisterFilteredComboboxesSource();
		}

		#endregion
		#region + Logic +

		public override bool CheckForConfig(bool correct, out Config incorrectConfig)
		{
			incorrectConfig = null;
			return true;
		}

		public override bool FilterRecord(IAudioBook audioBook)
		{
			bool result = (string.IsNullOrEmpty(Author) || Filter(Author, audioBook.Author))
								&& (string.IsNullOrEmpty(Genre) || Filter(Genre, audioBook.Genre))
								&& (string.IsNullOrEmpty(Bitrate) || Filter(Bitrate, audioBook.Bitrate))
								&& (string.IsNullOrEmpty(Description) || Filter(Description, audioBook.Description))
								&& (string.IsNullOrEmpty(ParsedLenght) || FilterTimeSpan(ParsedLenght, audioBook.ParsedLenght))
								&& (string.IsNullOrEmpty(Publisher) || Filter(Publisher, audioBook.Publisher))
								&& (string.IsNullOrEmpty(Reader) || Filter(Reader, audioBook.Reader))
								&& (string.IsNullOrEmpty(Title) || Filter(Title, audioBook.Title));

			if (!result) return false;

			if (!IsHidden)
			{
				bool isHidden;
				if (!_hiddenCache.TryGetValue(audioBook.Key, out isHidden))
				{
					UpdateCacheInternal(UpdateTypeEnum.Hidden);
					if (!_hiddenCache.TryGetValue(audioBook.Key, out isHidden)) return false;
				}
				if (isHidden) return false;
			}

			if (!IsLoaded)
			{
				bool isLoaded;
				if (!_loadedCache.TryGetValue(audioBook.Key, out isLoaded))
				{
					UpdateCacheInternal(UpdateTypeEnum.Loaded);
					if (!_loadedCache.TryGetValue(audioBook.Key, out isLoaded)) return false;
				}
				if (isLoaded) return false;
			}

			return result;
		}

		protected override void UpdateCacheInternal(UpdateTypeEnum updateType)
		{
			using (var dbContainer = Context.CreateDbContainer (false))
			{
				if ((updateType & UpdateTypeEnum.Values) == UpdateTypeEnum.Values)
				{
					_booksCache = dbContainer.AudioBookSet.GetRecordsAll().ToArray();

					FillFilterValueCollection("Author", _booksCache.Select(item => item.Author).Distinct().OrderBy(item => item));
					FillFilterValueCollection("Bitrate", _booksCache.Select(item => item.Bitrate).Distinct().OrderBy(item => item));
					FillFilterValueCollection("Genre", _booksCache.Select(item => item.Genre).Distinct().OrderBy(item => item));
					FillFilterValueCollection("Publisher", _booksCache.Select(item => item.Publisher).Distinct().OrderBy(item => item));
					FillFilterValueCollection("Reader", _booksCache.Select(item => item.Reader).Distinct().OrderBy(item => item));
				}

				bool updateHidden = (updateType & UpdateTypeEnum.Hidden) == UpdateTypeEnum.Hidden;
				bool updateLoaded = (updateType & UpdateTypeEnum.Loaded) == UpdateTypeEnum.Loaded;

				if (updateLoaded || updateHidden)
				{
					Dictionary<string, string> keys = dbContainer.AudioBookSet.GetRecordsAll().ToDictionary(item => item.GroupKey + "\\" + item.Key, item => item.Key);

					if (updateHidden)
					{
						_hiddenCache = keys.ToDictionary(item => item.Value, item => false);

						foreach (var hiddenRecord in dbContainer.HiddenRecordSet.GetHiddenRecordsAll())
						{
							string recordKey;
							string key = hiddenRecord.RecordGroupKey + "\\" + hiddenRecord.RecordKey;
							if (keys.TryGetValue(key, out recordKey))
							{
								_hiddenCache[recordKey] = true;
							}
						}

						var replacementStrings = dbContainer.ReplacementStringSet.GetReplacementStringsAll().GroupBy(item => item.ReplaceValue);

						foreach (var hiddenValue in dbContainer.HiddenValueSet.GetHiddenValuesAll())
						{
							var replacementString = replacementStrings.FirstOrDefault(item => item.Key == hiddenValue.Value);
							if (replacementString == null) continue;

							var hiddenValues = new HashSet<string>(replacementString.Select(item => item.PossibleValue));
							hiddenValues.Add(hiddenValue.Value);

							foreach (var audioBook in _booksCache)
							{
								if (_hiddenCache[audioBook.Key]) continue;

								bool hidden = false;

								string value = null;
								switch (hiddenValue.PropertyName)
								{
									case "bitrate":
										value = audioBook.Bitrate;
										break;
									case "publisher":
										value = audioBook.Publisher;
										break;
									case "reader":
										value = audioBook.Reader;
										break;
									case "author":
										value = audioBook.Author;
										break;
									case "genre":
										value = audioBook.Genre;
										break;
								}

								if (!value.IsNullOrEmpty())
								{
									if (hiddenValue.IgnoreCase)
									{
										if (hiddenValue.WholeWord)
										{
											foreach (var hiddenValue1 in hiddenValues)
											{
												hidden = String.Compare(value, hiddenValue1, StringComparison.OrdinalIgnoreCase) == 0;
												if (hidden) break;
											}
										}
										else
										{
											foreach (var hiddenValue1 in hiddenValues)
											{
												hidden = value.IndexOf(hiddenValue1, StringComparison.OrdinalIgnoreCase) >= 0;
												if (hidden) break;
											}
										}
									}
									else
									{
										if (hiddenValue.WholeWord)
										{
											foreach (var hiddenValue1 in hiddenValues)
											{
												hidden = String.CompareOrdinal(value, hiddenValue1) == 0;
												if (hidden) break;
											}
										}
										else
										{
											foreach (var hiddenValue1 in hiddenValues)
											{
												hidden = value.IndexOf(hiddenValue1, StringComparison.Ordinal) >= 0;
												if (hidden) break;
											}
										}
									}
								}
								_hiddenCache[audioBook.Key] = hidden;
							}
						}
					}

					if (updateLoaded)
					{
						_loadedCache = keys.ToDictionary(item => item.Value, item => false);

						foreach (var loadedRecord in dbContainer.UserDataSet.GetUserDataAll())
						{
							string recordKey;
							string key = loadedRecord.RecordGroupKey + "\\" + loadedRecord.RecordKey;
							if (keys.TryGetValue(key, out recordKey))
							{
								_loadedCache[recordKey] = true;
							}
						}
					}
				}
			}
		}

		private void FillFilterValueCollection(string collectionName, IEnumerable<string> filterValues)
		{
			var filterCollection = GetFilterValuesFromCache(collectionName);
			filterValues = filterValues.ToArray();
			var application = Application.Current;
			if (application != null)
			{
				application.Dispatcher.BeginInvoke((Action)(() =>
					{
						filterCollection.Clear();
						foreach (var filterValue in filterValues)
						{
							filterCollection.Add(filterValue);
						}
					}));
			}
		}

		private bool Filter(string filterString, object value)
		{
			return value != null && value.ToString().IndexOf(filterString, StringComparison.InvariantCultureIgnoreCase) >= 0;
		}

		private bool FilterTimeSpan(string filter, TimeSpan value)
		{
			bool result = false;

			if (filter.IsNullOrEmpty()) result = true;
			else
			{
				if (filter.StartsWith(">"))
				{
					var filterValue = filter.Substring(1, filter.Length - 1).ToTimeSpan();
					result = value > filterValue;
				}
				else if (filter.StartsWith("<"))
				{
					var filterValue = filter.Substring(1, filter.Length - 1).ToTimeSpan();
					result = value < filterValue;
				}
				else if (filter.StartsWith("="))
				{
					var filterValue = filter.Substring(1, filter.Length - 1).ToTimeSpan();
					result = value == filterValue;
				}
				else
				{
					var filterValue = filter.ToTimeSpan();
					result = value == filterValue;
				}
			}

			return result;
		}

		private ObservableCollection<string> GetFilterValuesFromCache(string setName)
		{
			ObservableCollection<string> result = null;
			if (!_filterValuesCache.TryGetValue(setName, out result))
			{
				result = new ObservableCollection<string>();
				if (!_filterValuesCache.TryAdd(setName, result))
				{
					result = _filterValuesCache[setName];
				}
			}
			return result;
		}

		private void RegisterFilteredComboboxesSource()
		{
			FilteredComboBoxEditor.Register("Author", GetFilterValuesFromCache("Author"));
			FilteredComboBoxEditor.Register("Bitrate", GetFilterValuesFromCache("Bitrate"));
			FilteredComboBoxEditor.Register("Genre", GetFilterValuesFromCache("Genre"));
			FilteredComboBoxEditor.Register("Publisher", GetFilterValuesFromCache("Publisher"));
			FilteredComboBoxEditor.Register("Reader", GetFilterValuesFromCache("Reader"));
		}

		#endregion
	}
}